今天讀完了 React 官方文件的 Synchronizing with Effects 的章節,初步學習了 Effects 的相關知識,透過實作來練習和理解觀念吧。
透過今天的練習會學習到的內容:
* 本篇還不會提到 cleanup 的操作
今天的練習也是出自官方文件。這個練習要讓 input 欄位顯示在focus
的狀態,並可以馬上輸入的狀態。也就是讓畫面從這樣子:
變成這樣子:
現有程式碼:
import { useEffect, useRef } from "react";
export default function MyInput({ value, onChange }) {
const ref = useRef(null);
// TODO: This doesn't quite work. Fix it.
// ref.current.focus()
return <input ref={ref} value={value} onChange={onChange} />;
}
原始的程式碼使用ref.current.focus()
但並未成功。
使用ref.current.focus()
不成功的的原因是這個屬於「副作用」(side effect),在 React 元件內部,我們定義了樣式、事件處理程序、狀態等等,這些都是 React 虛擬 DOM 的一部分。當元件的狀態(state)或屬性(props)發生變化時,React 會根據這些變化重新計算虛擬 DOM 的內容。
然而,當我們需要直接操作瀏覽器的 DOM 時,例如設置焦點、改變元素樣式、訂閱事件等,這就牽涉到了和外部世界(實際的瀏覽器 DOM)的交互。這些操作是 React 無法自動追蹤和管理的,因為它們不在 React 虛擬 DOM 的範疇內。因此,這些操作被稱為副作用。
在 React 當中處理副作用時我們應該使用的是useEffect
。useEffect 的 syntax 是這樣子:
useEffect(() => {
// 執行副作用操作的程式碼
return () => {
// 在組件被卸載(unmount)時執行的清理操作
};
}, [dependencies]);
因此僅需要將目前程式碼的ref.current.focus()
放進useEffect
function 當中並,設定依賴(dependencies) 為[]
empty string。即可完成這項小練習。
依賴(dependencies)的功能是限制 useEffect 的觸發,當有設定 dependencies 的情況下,程式碼僅會在依賴陣列的值發生變化時執行。
譬如假設我們設定了二個 dependencies:
useEffect(() => {
// 這裡的程式碼將在依賴陣列中的值發生變化時執行
}, [dependency1, dependency2]);
只有當 dependency1
或 dependency2
中的任何一個值發生變化時,useEffect
中的程式碼才會被執行。這樣可以精確地控制副作用的執行時機,確保它們只在特定的依賴值發生變化時執行,而不是在每次渲染時都執行。
那回到練習中完成的程式碼,在這邊我們將依賴(dependencies)設定為 empty string 的意義是什麼呢?
import { useEffect, useRef } from "react";
export default function MyInput({ value, onChange }) {
const ref = useRef(null);
// TODO: This doesn't quite work. Fix it.
// ref.current.focus()
useEffect(() => {
ref.current.focus();
}, []);
return <input ref={ref} value={value} onChange={onChange} />;
}
當我們將 dependencies 設定為 empty string 它代表的是「程式碼僅在掛載(mounting)時執行(當元件出現時)」。也就是說空陣列表示沒有依賴,只在組件第一次渲染時執行。
如果沒有依賴,而依賴也是選擇性(optional)參數並非必要,那麼直接不要寫 dependencies 讓程式碼維持這樣不好嗎?
useEffect(() => {
ref.current.focus();
});
還有將這個空陣列寫出來的意義嗎?
這的確是相當容易混淆的地方,React 官方文件也針對這點提供了相關的說明(見 pitfall 處):
useEffect(() => {
// 這會在每次渲染後執行
});
useEffect(() => {
// 這僅在掛載時執行(當組件出現時)
}, []);
useEffect(() => {
// 這在掛載時執行 *並且* 在上一次渲染後如果 a 或 b 有變化時也會執行
}, [a, b]);
假設如果不設定依賴陣列,useEffect
會在每次渲染後都執行, ref.current.focus()
將會每次都被呼叫,導致 input 在每次渲染後都被聚焦(focused),而不僅僅是元件掛載(mounting)時,所以useEffect
中的 ref.current.focus()
操作在每次組件重新渲染時都被執行,即使 input 元素已經聚焦,也會再次被聚焦。這樣子可能會造成不必要的網頁負擔和不必要的 DOM 更新。
以上是今天的學習,目前還沒有提到 clean up 的概念,明天會繼續看其他的例子來學習 useEffect 的應用。